use serde::de::DeserializeOwned;
use std::{fs::File, io::Read};
use tracing::info;

use regex::Regex;
use serde_json::Value;

pub fn load_json_file(path: &str) -> Result<Value, String> {
    let file = File::open(path);
    if file.is_err() {
        return Err(format!("failed to open file: {}", file.err().unwrap()));
    }

    let mut data = String::new();
    file.unwrap()
        .read_to_string(&mut data)
        .map_err(|e| e.to_string())?;
    let json_value: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?;

    Ok(json_value)
}

// robust parse LLM outtput into json

/// Attempt to robustly extract and parse JSON from arbitrary LLM output.
///
/// Conservative strategy:
/// 1. Try parsing the whole string directly.
/// 2. Extract code-fence contents (```...``` and ```json ...```) as candidates.
/// 3. Extract any balanced `{...}` or `[...]` substrings (skip braces inside double quotes).
/// 4. Try parsing candidates directly.
/// 5. If that fails, perform conservative cleanups (smart quotes -> " and remove trailing commas)
///    and try again. Only replace single quotes with double quotes as a last resort and only when
///    the candidate contains no double quotes (very conservative).
/// 6. If multiple candidates succeed, return them as an array.
pub fn robust_parse_json<T>(input: &str) -> Result<T, String>
where
    T: DeserializeOwned,
{
    // 1. Normalize smart quotes in the full input first
    let smart_fixed = input
        .replace('“', "\"")
        .replace('”', "\"")
        .replace('‘', "'")
        .replace('’', "'");

    // 2. Extract JSON candidates
    let mut candidates = extract_code_fence_contents(&smart_fixed);
    candidates.extend(extract_bracket_candidates(&smart_fixed));

    // Remove duplicates and empty strings
    let candidates: Vec<_> = dedupe_preserve_order(
        candidates
            .into_iter()
            .filter(|s| !s.trim().is_empty())
            .collect(),
    );

    if candidates.is_empty() {
        return Err("No JSON candidates found in input.".to_string());
    }

    // 3. Try parsing each candidate, track last error
    let mut last_err_msg: Option<String> = None;
    for cand in &candidates {
        let cand = cand
            .replace('“', "\"")
            .replace('”', "\"")
            .replace('‘', "'")
            .replace('’', "'")
            .trim()
            .trim_matches('`')
            .to_string();

        // Attempt parse in one pass with cleaning
        let parse_attempts = [cand.clone(), clean_trailing_commas(&cand), {
            if !cand.contains('"') && cand.contains('\'') {
                clean_trailing_commas(&cand.replace('\'', "\""))
            } else {
                String::new()
            }
        }];

        for attempt in parse_attempts.iter().filter(|s| !s.is_empty()) {
            match serde_json::from_str::<T>(attempt) {
                Ok(v) => return Ok(v),
                Err(e) => last_err_msg = Some(e.to_string()), // store last error
            }
        }
    }

    // If none succeeded, return the last error
    Err(last_err_msg.unwrap_or_else(|| "Failed to parse JSON from input.".to_string()))
}

/* ----------------- helper functions ----------------- */

fn dedupe_preserve_order(mut items: Vec<String>) -> Vec<String> {
    use std::collections::HashSet;
    let mut seen = HashSet::new();
    items.retain(|s| seen.insert(s.clone()));
    items
}

/// Extract content found inside triple backtick fences (``` or ```json)
fn extract_code_fence_contents(s: &str) -> Vec<String> {
    // (?s) for dot matches newline (DOTALL)
    let re = Regex::new(r"(?s)```(?:json)?\s*(.*?)```").unwrap();
    re.captures_iter(s)
        .filter_map(|cap| cap.get(1))
        .map(|m| m.as_str().to_string())
        .collect()
}

/// Remove trailing commas before closing braces/brackets. Conservative regex.
fn clean_trailing_commas(s: &str) -> String {
    // pattern: comma followed by optional whitespace and then either ']' or '}'
    let re = Regex::new(r",\s*([\]\}])").unwrap();
    let result = re.replace_all(s, "$1").to_string();
    result
}

/// Extract balanced `{...}` or `[...]` substrings by scanning and respecting double-quoted strings.
/// This uses a stack and char-aware scanning to avoid counting braces inside double-quoted strings.
fn extract_bracket_candidates(s: &str) -> Vec<String> {
    let mut results = Vec::new();
    let bytes = s.as_bytes();
    let mut i = 0usize;
    while i < bytes.len() {
        let ch = bytes[i] as char;
        if ch == '{' || ch == '[' {
            if let Some(end) = find_matching_bracket(s, i) {
                results.push(s[i..=end].to_string());
                i = end + 1;
                continue;
            }
        }
        i += 1;
    }
    results
}

/// Find matching closing bracket for an opening brace at byte index `start`.
/// Returns index of the closing bracket (byte index). Skips braces that are inside double-quoted strings
/// and handles escape sequences inside strings.
fn find_matching_bracket(s: &str, start: usize) -> Option<usize> {
    let open_ch = s[start..].chars().next()?;
    let close_ch = match open_ch {
        '{' => '}',
        '[' => ']',
        _ => return None,
    };

    let mut depth: i32 = 0;
    let mut in_string = false;
    let mut escaped = false;

    // iterate by char indices so we can return a byte index
    for (idx, ch) in s.char_indices().skip_while(|(idx, _)| *idx < start) {
        if in_string {
            if escaped {
                escaped = false;
            } else if ch == '\\' {
                escaped = true;
            } else if ch == '"' {
                in_string = false;
            }
            continue;
        } else {
            if ch == '"' {
                in_string = true;
                continue;
            }
            if ch == open_ch {
                depth += 1;
            } else if ch == close_ch {
                depth -= 1;
                if depth == 0 {
                    return Some(idx);
                }
            }
        }
    }
    None
}
